Odkryj moc hooka useActionState w React. Dowiedz si臋, jak upraszcza zarz膮dzanie formularzami, obs艂uguje stany oczekiwania i poprawia UX dzi臋ki praktycznym przyk艂adom.
React useActionState: Kompleksowy przewodnik po nowoczesnym zarz膮dzaniu formularzami
艢wiat web developmentu nieustannie ewoluuje, a ekosystem Reacta jest na czele tej zmiany. W najnowszych wersjach React wprowadzi艂 pot臋偶ne funkcje, kt贸re fundamentalnie ulepszaj膮 spos贸b, w jaki budujemy interaktywne i odporne na b艂臋dy aplikacje. Jedn膮 z najbardziej wp艂ywowych z nich jest hook useActionState, kt贸ry rewolucjonizuje obs艂ug臋 formularzy i operacji asynchronicznych. Ten hook, wcze艣niej znany jako useFormState w wersjach eksperymentalnych, jest teraz stabilnym i niezb臋dnym narz臋dziem dla ka偶dego nowoczesnego dewelopera Reacta.
Ten kompleksowy przewodnik zabierze Ci臋 w g艂膮b useActionState. Zbadamy problemy, kt贸re rozwi膮zuje, jego podstawowe mechanizmy oraz jak wykorzysta膰 go w po艂膮czeniu z uzupe艂niaj膮cymi hookami, takimi jak useFormStatus, aby tworzy膰 doskona艂e do艣wiadczenia u偶ytkownika. Niezale偶nie od tego, czy budujesz prosty formularz kontaktowy, czy z艂o偶on膮 aplikacj臋 intensywnie korzystaj膮c膮 z danych, zrozumienie useActionState sprawi, 偶e Tw贸j kod b臋dzie czystszy, bardziej deklaratywny i solidniejszy.
Problem: Z艂o偶ono艣膰 tradycyjnego zarz膮dzania stanem formularza
Zanim docenimy elegancj臋 useActionState, musimy najpierw zrozumie膰 wyzwania, kt贸rym stawia czo艂a. Przez lata zarz膮dzanie stanem formularza w React wi膮za艂o si臋 z przewidywalnym, ale cz臋sto uci膮偶liwym wzorcem wykorzystuj膮cym hook useState.
Rozwa偶my typowy scenariusz: prosty formularz do dodawania nowego produktu do listy. Musimy zarz膮dza膰 kilkoma elementami stanu:
- Warto艣ci膮 pola do wprowadzania nazwy produktu.
- Stanem 艂adowania lub oczekiwania, aby da膰 u偶ytkownikowi informacj臋 zwrotn膮 podczas wywo艂ania API.
- Stanem b艂臋du, aby wy艣wietla膰 komunikaty, je艣li przes艂anie si臋 nie powiedzie.
- Stanem sukcesu lub komunikatem po zako艅czeniu.
Typowa implementacja mog艂aby wygl膮da膰 mniej wi臋cej tak:
Przyk艂ad: 'Stary spos贸b' z wieloma hookami useState
// Fikcyjna funkcja API
const addProductAPI = async (productName) => {
await new Promise(resolve => setTimeout(resolve, 1500));
if (!productName || productName.length < 3) {
throw new Error('Nazwa produktu musi mie膰 co najmniej 3 znaki.');
}
console.log(`Produkt "${productName}" dodany.`);
return { success: true };
};
// Komponent
{error}import { useState } from 'react';
function OldProductForm() {
const [productName, setProductName] = useState('');
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setIsPending(true);
setError(null);
try {
await addProductAPI(productName);
setProductName(''); // Wyczy艣膰 pole po sukcesie
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
};
return (
id="productName"
name="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
/>
{isPending ? 'Dodawanie...' : 'Dodaj produkt'}
{error &&
);
}
To podej艣cie dzia艂a, ale ma kilka wad:
- Nadmiarowy kod: Potrzebujemy trzech osobnych wywo艂a艅 useState, aby zarz膮dza膰 czym艣, co koncepcyjnie jest pojedynczym procesem przesy艂ania formularza.
- R臋czne zarz膮dzanie stanem: Deweloper jest odpowiedzialny za r臋czne ustawianie i resetowanie stan贸w 艂adowania i b艂臋du w odpowiedniej kolejno艣ci w bloku try...catch...finally. Jest to powtarzalne i podatne na b艂臋dy.
- 艢cis艂e powi膮zanie: Logika obs艂ugi wyniku przesy艂ania formularza jest 艣ci艣le powi膮zana z logik膮 renderowania komponentu.
Wprowadzenie useActionState: Zmiana paradygmatu
useActionState to hook Reacta zaprojektowany specjalnie do zarz膮dzania stanem operacji asynchronicznej, takiej jak przesy艂anie formularza. Usprawnia on ca艂y proces, 艂膮cz膮c stan bezpo艣rednio z wynikiem funkcji akcji.
Jego sygnatura jest jasna i zwi臋z艂a:
const [state, formAction] = useActionState(actionFn, initialState);
Roz艂贸偶my go na cz臋艣ci:
actionFn(previousState, formData)
: To Twoja funkcja asynchroniczna, kt贸ra wykonuje prac臋 (np. wywo艂uje API). Otrzymuje ona poprzedni stan i dane formularza jako argumenty. Co kluczowe, to, co ta funkcja zwraca, staje si臋 nowym stanem.initialState
: To jest warto艣膰 stanu, zanim akcja zostanie wykonana po raz pierwszy.state
: To jest bie偶膮cy stan. Pocz膮tkowo przechowuje initialState i jest aktualizowany do warto艣ci zwrotnej Twojej funkcji actionFn po ka偶dym wykonaniu.formAction
: To nowa, opakowana wersja Twojej funkcji akcji. Powiniene艣 przekaza膰 t臋 funkcj臋 do propaaction
elementu<form>
. React u偶ywa tej opakowanej funkcji do 艣ledzenia stanu oczekiwania (pending) akcji.
Praktyczny przyk艂ad: Refaktoryzacja z useActionState
Teraz zrefaktoryzujmy nasz formularz produktu przy u偶yciu useActionState. Poprawa jest natychmiast widoczna.
Najpierw musimy dostosowa膰 nasz膮 logik臋 akcji. Zamiast rzuca膰 b艂臋dy, akcja powinna zwraca膰 obiekt stanu, kt贸ry opisuje wynik.
Przyk艂ad: 'Nowy spos贸b' z useActionState
// Funkcja akcji, zaprojektowana do pracy z useActionState
const addProductAction = async (previousState, formData) => {
const productName = formData.get('productName');
await new Promise(resolve => setTimeout(resolve, 1500)); // Symulacja op贸藕nienia sieciowego
if (!productName || productName.length < 3) {
return { message: 'Nazwa produktu musi mie膰 co najmniej 3 znaki.', success: false };
}
console.log(`Produkt "${productName}" dodany.`);
// Po sukcesie zwr贸膰 komunikat i wyczy艣膰 formularz.
return { message: `Pomy艣lnie dodano "${productName}"`, success: true };
};
// Zrefaktoryzowany komponent
{state.message} {state.message}import { useActionState } from 'react';
// Uwaga: W nast臋pnej sekcji dodamy useFormStatus do obs艂ugi stanu oczekiwania.
function NewProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);
return (
{!state.success && state.message && (
)}
{state.success && state.message && (
)}
);
}
Sp贸jrz, o ile to jest czystsze! Zast膮pili艣my trzy hooki useState jednym hookiem useActionState. Odpowiedzialno艣ci膮 komponentu jest teraz wy艂膮cznie renderowanie interfejsu u偶ytkownika na podstawie obiektu `state`. Ca艂a logika biznesowa jest zgrabnie zamkni臋ta w funkcji `addProductAction`. Stan aktualizuje si臋 automatycznie na podstawie tego, co zwraca akcja.
Ale zaraz, co ze stanem oczekiwania? Jak wy艂膮czy膰 przycisk, gdy formularz jest przesy艂any?
Obs艂uga stan贸w oczekiwania za pomoc膮 useFormStatus
React dostarcza towarzysz膮cego hooka, useFormStatus, zaprojektowanego do rozwi膮zania dok艂adnie tego problemu. Dostarcza on informacji o statusie ostatniego przesy艂ania formularza, ale z kluczow膮 zasad膮: musi by膰 wywo艂any z komponentu, kt贸ry jest renderowany wewn膮trz <form>
, kt贸rego status chcesz 艣ledzi膰.
To zach臋ca do czystego podzia艂u odpowiedzialno艣ci. Tworzysz komponent specjalnie dla element贸w interfejsu, kt贸re musz膮 by膰 艣wiadome statusu przesy艂ania formularza, jak przycisk do przesy艂ania.
Hook useFormStatus zwraca obiekt z kilkoma w艂a艣ciwo艣ciami, z kt贸rych najwa偶niejsz膮 jest `pending`.
const { pending, data, method, action } = useFormStatus();
pending
: Warto艣膰 logiczna, kt贸ra jest `true`, je艣li formularz nadrz臋dny jest aktualnie przesy艂any, a `false` w przeciwnym razie.data
: Obiekt `FormData` zawieraj膮cy przesy艂ane dane.method
: Ci膮g znak贸w wskazuj膮cy metod臋 HTTP (`'get'` lub `'post'`).action
: Odniesienie do funkcji przekazanej do propa `action` formularza.
Tworzenie przycisku przesy艂ania 艣wiadomego statusu
Stw贸rzmy dedykowany komponent `SubmitButton` i zintegrujmy go z naszym formularzem.
Przyk艂ad: Komponent SubmitButton
import { useFormStatus } from 'react-dom';
// Uwaga: useFormStatus jest importowane z 'react-dom', a nie z 'react'.
function SubmitButton() {
const { pending } = useFormStatus();
return (
{pending ? 'Dodawanie...' : 'Dodaj produkt'}
);
}
Teraz mo偶emy zaktualizowa膰 nasz g艂贸wny komponent formularza, aby go u偶y膰.
Przyk艂ad: Kompletny formularz z useActionState i useFormStatus
{state.message} {state.message}import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
// ... (funkcja addProductAction pozostaje bez zmian)
function SubmitButton() { /* ... jak zdefiniowano powy偶ej ... */ }
function CompleteProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);
return (
{/* Mo偶emy doda膰 klucz 'key', aby zresetowa膰 pole po sukcesie */}
{!state.success && state.message && (
)}
{state.success && state.message && (
)}
);
}
Dzi臋ki tej strukturze komponent `CompleteProductForm` nie musi nic wiedzie膰 o stanie oczekiwania. `SubmitButton` jest ca艂kowicie samowystarczalny. Ten kompozycyjny wzorzec jest niezwykle pot臋偶ny do budowania z艂o偶onych, 艂atwych w utrzymaniu interfejs贸w u偶ytkownika.
Moc progresywnego ulepszania (Progressive Enhancement)
Jedn膮 z najg艂臋bszych korzy艣ci tego nowego podej艣cia opartego na akcjach, zw艂aszcza w po艂膮czeniu z Akcjami Serwerowymi (Server Actions), jest automatyczne progresywne ulepszanie. Jest to kluczowa koncepcja budowania aplikacji dla globalnej publiczno艣ci, gdzie warunki sieciowe mog膮 by膰 niestabilne, a u偶ytkownicy mog膮 mie膰 starsze urz膮dzenia lub wy艂膮czony JavaScript.
Oto jak to dzia艂a:
- Bez JavaScriptu: Je艣li przegl膮darka u偶ytkownika nie wykonuje JavaScriptu po stronie klienta,
<form action={...}>
dzia艂a jak standardowy formularz HTML. Wykonuje pe艂ne prze艂adowanie strony z 偶膮daniem do serwera. Je艣li u偶ywasz frameworka takiego jak Next.js, akcja po stronie serwera jest uruchamiana, a framework renderuje ca艂膮 stron臋 na nowo z nowym stanem (np. pokazuj膮c b艂膮d walidacji). Aplikacja jest w pe艂ni funkcjonalna, tylko bez p艂ynno艣ci charakterystycznej dla SPA. - Z JavaScriptem: Gdy pakiet JavaScript zostanie za艂adowany i React nawodni stron臋, ta sama funkcja `formAction` jest wykonywana po stronie klienta. Zamiast pe艂nego prze艂adowania strony, zachowuje si臋 jak typowe 偶膮danie fetch. Akcja jest wywo艂ywana, stan jest aktualizowany i tylko niezb臋dne cz臋艣ci komponentu s膮 ponownie renderowane.
Oznacza to, 偶e piszesz logik臋 formularza raz, a dzia艂a ona bezproblemowo w obu scenariuszach. Domy艣lnie budujesz odporn膮 i dost臋pn膮 aplikacj臋, co jest ogromn膮 wygran膮 dla do艣wiadczenia u偶ytkownika na ca艂ym 艣wiecie.
Zaawansowane wzorce i przypadki u偶ycia
1. Akcje serwerowe kontra akcje klienckie
Funkcja `actionFn`, kt贸r膮 przekazujesz do useActionState, mo偶e by膰 standardow膮 funkcj膮 asynchroniczn膮 po stronie klienta (jak w naszych przyk艂adach) lub Akcj膮 Serwerow膮. Akcja Serwerowa to funkcja zdefiniowana na serwerze, kt贸r膮 mo偶na wywo艂ywa膰 bezpo艣rednio z komponent贸w klienckich. W frameworkach takich jak Next.js, definiuje si臋 j膮, dodaj膮c dyrektyw臋 "use server";
na pocz膮tku cia艂a funkcji.
- Akcje klienckie: Idealne do mutacji, kt贸re wp艂ywaj膮 tylko na stan po stronie klienta lub wywo艂uj膮 API firm trzecich bezpo艣rednio z klienta.
- Akcje serwerowe: Doskona艂e do mutacji, kt贸re obejmuj膮 baz臋 danych lub inne zasoby po stronie serwera. Upraszczaj膮 architektur臋, eliminuj膮c potrzeb臋 r臋cznego tworzenia punkt贸w ko艅cowych API dla ka偶dej mutacji.
Pi臋kno polega na tym, 偶e useActionState dzia艂a identycznie z oboma typami akcji. Mo偶esz zamieni膰 akcj臋 klienck膮 na serwerow膮 bez zmiany kodu komponentu.
2. Optymistyczne aktualizacje z `useOptimistic`
Aby uzyska膰 jeszcze bardziej responsywne odczucia, mo偶na po艂膮czy膰 useActionState z hookiem useOptimistic. Optymistyczna aktualizacja polega na natychmiastowej aktualizacji interfejsu u偶ytkownika, *zak艂adaj膮c*, 偶e operacja asynchroniczna zako艅czy si臋 sukcesem. Je艣li si臋 nie powiedzie, przywracasz interfejs do poprzedniego stanu.
Wyobra藕 sobie aplikacj臋 spo艂eczno艣ciow膮, w kt贸rej dodajesz komentarz. Optymistycznie pokaza艂by艣 nowy komentarz na li艣cie natychmiast, podczas gdy 偶膮danie jest wysy艂ane na serwer. useOptimistic jest zaprojektowany do wsp贸艂pracy z akcjami, aby ten wzorzec by艂 prosty do zaimplementowania.
3. Resetowanie formularza po sukcesie
Cz臋stym wymogiem jest czyszczenie p贸l formularza po udanym przes艂aniu. Istnieje kilka sposob贸w, aby to osi膮gn膮膰 z useActionState.
- Sztuczka z propem 'key': Jak pokazano w naszym przyk艂adzie `CompleteProductForm`, mo偶na przypisa膰 unikalny `key` do pola input lub ca艂ego formularza. Gdy klucz si臋 zmienia, React odmontowuje stary komponent i montuje nowy, skutecznie resetuj膮c jego stan. Powi膮zanie klucza z flag膮 sukcesu (`key={state.success ? 'success' : 'initial'}`) jest prost膮 i skuteczn膮 metod膮.
- Komponenty kontrolowane: W razie potrzeby nadal mo偶na u偶ywa膰 komponent贸w kontrolowanych. Zarz膮dzaj膮c warto艣ci膮 pola za pomoc膮 useState, mo偶na wywo艂a膰 funkcj臋 ustawiaj膮c膮, aby j膮 wyczy艣ci膰 wewn膮trz useEffect, kt贸ry nas艂uchuje na stan sukcesu z useActionState.
Cz臋ste pu艂apki i dobre praktyki
- Umiejscowienie
useFormStatus
: Pami臋taj, 偶e komponent wywo艂uj膮cy useFormStatus musi by膰 renderowany jako dziecko<form>
. Nie zadzia艂a, je艣li b臋dzie rodze艅stwem lub rodzicem. - Stan serializowalny: Podczas korzystania z Akcji Serwerowych, obiekt stanu zwracany z Twojej akcji musi by膰 serializowalny. Oznacza to, 偶e nie mo偶e zawiera膰 funkcji, Symboli ani innych warto艣ci nieserializowalnych. Trzymaj si臋 prostych obiekt贸w, tablic, ci膮g贸w znak贸w, liczb i warto艣ci logicznych.
- Nie rzucaj b艂臋d贸w w akcjach: Zamiast `throw new Error()`, Twoja funkcja akcji powinna elegancko obs艂ugiwa膰 b艂臋dy i zwraca膰 obiekt stanu, kt贸ry opisuje b艂膮d (np. `{ success: false, message: 'Wyst膮pi艂 b艂膮d' }`). Zapewnia to, 偶e stan jest zawsze aktualizowany w przewidywalny spos贸b.
- Zdefiniuj jasny kszta艂t stanu: Ustal sp贸jn膮 struktur臋 dla swojego obiektu stanu od samego pocz膮tku. Kszta艂t taki jak `{ data: T | null, message: string | null, success: boolean, errors: Record
| null }` mo偶e pokry膰 wiele przypadk贸w u偶ycia.
useActionState kontra useReducer: Szybkie por贸wnanie
Na pierwszy rzut oka useActionState mo偶e wydawa膰 si臋 podobny do useReducer, poniewa偶 oba obejmuj膮 aktualizacj臋 stanu na podstawie poprzedniego stanu. S艂u偶膮 jednak do odr臋bnych cel贸w.
useReducer
to uniwersalny hook do zarz膮dzania z艂o偶onymi przej艣ciami stanu po stronie klienta. Jest wyzwalany przez wysy艂anie akcji i jest idealny do logiki stanu, kt贸ra ma wiele mo偶liwych, synchronicznych zmian stanu (np. z艂o偶ony kreator wieloetapowy).useActionState
to wyspecjalizowany hook przeznaczony do stanu, kt贸ry zmienia si臋 w odpowiedzi na pojedyncz膮, zazwyczaj asynchroniczn膮 akcj臋. Jego g艂贸wn膮 rol膮 jest integracja z formularzami HTML, Akcjami Serwerowymi i funkcjami renderowania wsp贸艂bie偶nego Reacta, takimi jak przej艣cia stanu oczekiwania.
Wniosek: Do przesy艂ania formularzy i operacji asynchronicznych powi膮zanych z formularzami, useActionState jest nowoczesnym, specjalnie do tego celu stworzonym narz臋dziem. Do innych z艂o偶onych maszyn stan贸w po stronie klienta, useReducer pozostaje doskona艂ym wyborem.
Podsumowanie: Przyj臋cie przysz艂o艣ci formularzy w React
Hook useActionState to co艣 wi臋cej ni偶 tylko nowe API; reprezentuje on fundamentaln膮 zmian臋 w kierunku bardziej solidnego, deklaratywnego i zorientowanego na u偶ytkownika sposobu obs艂ugi formularzy i mutacji danych w React. Przyjmuj膮c go, zyskujesz:
- Mniej kodu szablonowego: Pojedynczy hook zast臋puje wielokrotne wywo艂ania useState i r臋czn膮 orkiestracj臋 stanu.
- Zintegrowane stany oczekiwania: Bezproblemowo obs艂uguj interfejsy 艂adowania za pomoc膮 towarzysz膮cego hooka useFormStatus.
- Wbudowane progresywne ulepszanie: Pisz kod, kt贸ry dzia艂a z JavaScriptem lub bez, zapewniaj膮c dost臋pno艣膰 i odporno艣膰 dla wszystkich u偶ytkownik贸w.
- Uproszczona komunikacja z serwerem: Naturalne dopasowanie do Akcji Serwerowych, usprawniaj膮ce do艣wiadczenie rozwoju full-stack.
Rozpoczynaj膮c nowe projekty lub refaktoryzuj膮c istniej膮ce, rozwa偶 si臋gni臋cie po useActionState. Nie tylko poprawi to Twoje do艣wiadczenie deweloperskie, czyni膮c kod czystszym i bardziej przewidywalnym, ale tak偶e umo偶liwi Ci budowanie aplikacji wy偶szej jako艣ci, kt贸re s膮 szybsze, bardziej odporne i dost臋pne dla zr贸偶nicowanej globalnej publiczno艣ci.